package com.lixixi.tingshu.search.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.RandomUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldSort;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsBucket;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.MultiMatchQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.NestedQuery;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.*;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.JSONObject;
import com.lixixi.tingshu.album.api.AlbumApiFeignClient;
import com.lixixi.tingshu.common.constant.KafkaConstant;
import com.lixixi.tingshu.common.constant.RedisConstant;
import com.lixixi.tingshu.common.result.Result;
import com.lixixi.tingshu.common.util.PinYinUtils;
import com.lixixi.tingshu.model.album.AlbumAttributeValue;
import com.lixixi.tingshu.model.album.AlbumInfo;
import com.lixixi.tingshu.model.album.BaseCategory3;
import com.lixixi.tingshu.model.album.BaseCategoryView;
import com.lixixi.tingshu.model.search.AlbumInfoIndex;
import com.lixixi.tingshu.model.search.AttributeValueIndex;
import com.lixixi.tingshu.model.search.SuggestIndex;
import com.lixixi.tingshu.query.search.AlbumIndexQuery;
import com.lixixi.tingshu.search.respository.AlbumInfoRespository;
import com.lixixi.tingshu.search.respository.AlbumInfoSuggestIndexRespository;
import com.lixixi.tingshu.search.service.SearchAlbumInfoService;
import com.lixixi.tingshu.user.api.UserApiFeignClient;
import com.lixixi.tingshu.vo.album.AlbumStatVo;
import com.lixixi.tingshu.vo.search.AlbumInfoIndexVo;
import com.lixixi.tingshu.vo.search.AlbumSearchResponseVo;
import com.lixixi.tingshu.vo.user.UserInfoVo;
import io.netty.util.concurrent.DefaultThreadFactory;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Slf4j
@Service
public class SearchAlbumInfoServiceImpl implements SearchAlbumInfoService {

	@Autowired
	ThreadPoolTaskExecutor executorService;

	private final String ELASTICSEARCH_INDEX_NAME = "albuminfo_index";

	private final int SUGGEST_LIMIT_LENGTH = 10;

	@Autowired
	AlbumInfoRespository albumInfoRespository;

	@Autowired
	KafkaTemplate kafkaTemplate;

	@Autowired
	AlbumInfoSuggestIndexRespository albumInfoSuggestIndexRespository;

	@Autowired
	AlbumApiFeignClient albumApiFeignClient;

	@Autowired
	UserApiFeignClient userApiFeignClient;

	@Autowired
	ElasticsearchClient elasticsearchClient;

	@Autowired
	RedisTemplate<String, String> redisTemplate;

	@Autowired
	RedissonClient redissonClient;

	@Override
	public void upperAlbum(Long albumId) {
		//创建索引库对象
		AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();
		List<CompletableFuture> list = new LinkedList<>();
		//获取专辑信息
		CompletableFuture<AlbumInfo> completableAlbumInfoFuture = CompletableFuture.supplyAsync(() -> {
			Result<AlbumInfo> albumInfoResult = albumApiFeignClient.getAlbumInfoById(albumId);
			return albumInfoResult.getData();
		}, executorService);
		list.add(completableAlbumInfoFuture);
		CompletableFuture<Long> completableUserIdFuture = completableAlbumInfoFuture.thenApply((albumInfo) -> {
			log.info(albumInfo.toString());
			//创建任务获取专辑分类
			CompletableFuture.runAsync(() -> {
				Result<BaseCategoryView> categoryViewResult = albumApiFeignClient.findCategoryByCategory3Id(albumInfo.getCategory3Id());
				BaseCategoryView categoryView = categoryViewResult.getData();
				log.info(categoryView.toString());
				//填充分类属性
				albumInfoIndex.setCategory1Id(categoryView.getCategory1Id());
				albumInfoIndex.setCategory2Id(categoryView.getCategory2Id());
				albumInfoIndex.setCategory3Id(categoryView.getCategory3Id());
			}, executorService);
			//填充专辑信息
			albumInfoIndex.setId(albumInfo.getId());
			albumInfoIndex.setAlbumTitle(albumInfo.getAlbumTitle());
			albumInfoIndex.setAlbumIntro(albumInfo.getAlbumIntro());
			albumInfoIndex.setCoverUrl(albumInfo.getCoverUrl());
			albumInfoIndex.setIncludeTrackCount(albumInfo.getIncludeTrackCount());
			albumInfoIndex.setPayType(albumInfo.getPayType());
			albumInfoIndex.setCreateTime(albumInfo.getCreateTime());
			albumInfoIndex.setIsFinished(albumInfo.getIsFinished() == 1 ? "是" : "否");
			//设置专辑属性值
			List<AttributeValueIndex> attributeValueIndexList = albumInfo.getAlbumAttributeValueVoList()
					.parallelStream()
					.map(albumAttributeValue -> {
						AttributeValueIndex attributeValueIndex = new AttributeValueIndex();
						attributeValueIndex.setAttributeId(albumAttributeValue.getAttributeId());
						attributeValueIndex.setValueId(albumAttributeValue.getValueId());
						return attributeValueIndex;
					}).toList();
			albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);
			return albumInfo.getUserId();
		});
		CompletableFuture<Void> completableSuggestFuture = completableUserIdFuture.thenAccept(userId -> {
			//创建任务获取用户名字
			//查询用户名字
			UserInfoVo userInfoVo = userApiFeignClient.getUserInfoForFeign(userId).getData();
			String username = userInfoVo.getNickname();
			log.info(username);
			//赋值用户名字
			albumInfoIndex.setAnnouncerName(username);
		});
		list.add(completableSuggestFuture);
		//获取专辑 '统计类型'
		CompletableFuture.runAsync(() -> {
			Result<AlbumStatVo> albumStatVoResult = albumApiFeignClient.getAlbumStatById(albumId);
			AlbumStatVo albumStatVo = albumStatVoResult.getData();
			log.info(albumStatVo.toString());
			//填充专辑统计类型
			albumInfoIndex.setPlayStatNum(albumStatVo.getPlayStatNum());
			albumInfoIndex.setSubscribeStatNum(albumStatVo.getSubscribeStatNum());
			albumInfoIndex.setBuyStatNum(albumStatVo.getBuyStatNum());
			albumInfoIndex.setCommentStatNum(albumStatVo.getCommentStatNum());
		}, executorService);
		//TODO:随机热度排名,用作测试
		albumInfoIndex.setHotScore(RandomUtil.randomDouble(0.0, 100.0, 4, RoundingMode.HALF_UP));
		CompletableFuture<Void> completableFuture = completableSuggestFuture.thenRun(() -> {
			//上架专辑
			executorService.submit(() -> albumInfoRespository.save(albumInfoIndex));
			//注册布隆过滤器存在该专辑
			executorService.submit(() -> {
				RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter(RedisConstant.ALBUM_BLOOM_FILTER);
				bloomFilter.add(albumId);
			});
			//注册专辑Suggesters建议器
			//注册专辑标题提词器
			executorService.submit(() -> {
				SuggestIndex albumInfoTitleToSuggestIndex = this.albumInfoIndexToSuggestIndex(albumInfoIndex, AlbumInfoIndex::getAlbumTitle);
				albumInfoSuggestIndexRespository.save(albumInfoTitleToSuggestIndex);
			});
			//注册专辑简介提词器
			executorService.submit(() -> {
				SuggestIndex albumInfoIntroSuggestIndex = this.albumInfoIndexToSuggestIndex(albumInfoIndex, AlbumInfoIndex::getAlbumIntro);
				albumInfoSuggestIndexRespository.save(albumInfoIntroSuggestIndex);
			});
			//注册专辑创建用户提词器
			executorService.submit(() -> {
				SuggestIndex albumInfoAnnouncerNameSuggestIndex = this.albumInfoIndexToSuggestIndex(albumInfoIndex, AlbumInfoIndex::getAnnouncerName);
				albumInfoSuggestIndexRespository.save(albumInfoAnnouncerNameSuggestIndex);
			});
		});
		list.add(completableFuture);
		list.forEach(CompletableFuture::join);
	}

	@SneakyThrows
	@Override
	public void lowerAlbum(Long albumId) {
		RLock lock = redissonClient.getLock(RedisConstant.LOWER_ALBUM_COUNT_LOCK);
		boolean tryLock = lock.tryLock(RedisConstant.ALBUM_LOCK_EXPIRE_PX1, RedisConstant.ALBUM_LOCK_EXPIRE_PX2, TimeUnit.SECONDS);
		try
		{
			if (tryLock)
			{
				//获取专辑下架数量
				String strCount = redisTemplate.opsForValue().get(RedisConstant.LOWER_ALBUM_COUNT);
				if (strCount == null)
				{
					strCount = String.valueOf(0);
					redisTemplate.opsForValue().set(RedisConstant.LOWER_ALBUM_COUNT, strCount);
				}
				if (Long.parseLong(strCount) > 500)
				{
					//发送消息重新部署布隆过滤器
					kafkaTemplate.send(KafkaConstant.QUEUE_ALBUM_FLUSH_BLOOM_FILTER, null);
					redisTemplate.opsForValue().set(RedisConstant.LOWER_ALBUM_COUNT, String.valueOf(0));
				} else
				{
					long count = Long.parseLong(strCount);
					redisTemplate.opsForValue().set(RedisConstant.LOWER_ALBUM_COUNT, String.valueOf(count + 1));
				}
			} else
			{
				return;
			}
		} catch (NumberFormatException e)
		{
			throw new RuntimeException(e);
		} finally
		{
			lock.unlock();
		}
		albumInfoRespository.deleteById(albumId);
	}

	@Override
	public void upperAlbumByBatch(Long userId) {
		//获取布隆过滤器
		RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter(RedisConstant.ALBUM_BLOOM_FILTER);
		//批量获取专辑信息
		Result<List<AlbumInfo>> albumInfoResult = albumApiFeignClient.getUserAlbumInfoByBatch(userId);//取出当前用户下的所有专辑id
		List<AlbumInfo> albumInfoList = albumInfoResult.getData();
		List<Long> albumIdList = albumInfoResult.getData().stream().map(AlbumInfo::getId).toList();
		//TODO:获取专辑 '统计类型'(开启批量处理)
		Result<Map<Long, List<AlbumStatVo>>> albumStatVoResult = albumApiFeignClient.getAlbumStatByIdBatch(albumIdList);
		//key为专辑id,value为对应专辑'统计类型'
		Map<Long, List<AlbumStatVo>> albumStatVoMap = albumStatVoResult.getData();
		Assert.notNull(albumInfoList, "专辑列表为空");
		//获取当前列表中所有专辑的三级分类id
		List<Long> category3IdList = albumInfoList.stream().map(AlbumInfo::getCategory3Id).toList();
		//批量获取专辑分类信息
		Result<Map<Long, List<BaseCategoryView>>> categoryViewResult = albumApiFeignClient.findCategoryByCategory3IdBatch(category3IdList);
		//key为3级分类id, value为3级分类信息
		Map<Long, List<BaseCategoryView>> categoryViewMap = categoryViewResult.getData();
		//查询用户名字
		UserInfoVo userInfoVo = userApiFeignClient.getUserInfoForFeign(userId).getData();
		String username = userInfoVo.getNickname();
		Assert.notNull(username, "用户名字为空");
		//作为存储专辑索引对象的容器
		List<AlbumInfoIndex> albumInfoIndexList = new LinkedList<>();
		//批量添加索引对象
		for (AlbumInfo albumInfo : albumInfoList)
		{
			//创建专辑索引对象
			AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();
			//填充专辑信息
			albumInfoIndex.setId(albumInfo.getId());
			albumInfoIndex.setAlbumTitle(albumInfo.getAlbumTitle());
			albumInfoIndex.setAlbumIntro(albumInfo.getAlbumIntro());
			//赋值用户名字
			albumInfoIndex.setAnnouncerName(username);
			albumInfoIndex.setCoverUrl(albumInfo.getCoverUrl());
			albumInfoIndex.setIncludeTrackCount(albumInfo.getIncludeTrackCount());
			albumInfoIndex.setIsFinished(albumInfoIndex.getIsFinished());
			albumInfoIndex.setPayType(albumInfo.getPayType());
			albumInfoIndex.setCreateTime(albumInfo.getCreateTime());
			//设置专辑属性值
			//获取当前专辑属性列表
			List<AlbumAttributeValue> albumAttributeValueVoList = albumInfo.getAlbumAttributeValueVoList();
			if (!CollectionUtils.isEmpty(albumAttributeValueVoList))
			{
				List<AttributeValueIndex> attributeValueIndexList = albumAttributeValueVoList.stream().map(albumAttributeValue -> {
					AttributeValueIndex attributeValueIndex = new AttributeValueIndex();
					attributeValueIndex.setAttributeId(albumAttributeValue.getAttributeId());
					attributeValueIndex.setValueId(albumAttributeValue.getValueId());
					return attributeValueIndex;
				}).toList();
				albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);
			}
			//TODO:根据 当前专辑3级分类id 获取 具体分类id
			BaseCategoryView categoryView = categoryViewMap.get(albumInfo.getCategory3Id()).get(0);
			//填充分类属性
			albumInfoIndex.setCategory1Id(categoryView.getCategory1Id());
			albumInfoIndex.setCategory2Id(categoryView.getCategory2Id());
			albumInfoIndex.setCategory3Id(categoryView.getCategory3Id());
			//TODO: 根据专辑id 获取当前专辑 '统计类型'
			AlbumStatVo albumStatVo = albumStatVoMap.get(albumInfo.getId()).get(0);
			Assert.notNull(albumStatVo, "专辑统计类型列表为空");
			//填充专辑统计类型
			albumInfoIndex.setPlayStatNum(albumStatVo.getPlayStatNum());
			albumInfoIndex.setSubscribeStatNum(albumStatVo.getSubscribeStatNum());
			albumInfoIndex.setBuyStatNum(albumStatVo.getBuyStatNum());
			albumInfoIndex.setCommentStatNum(albumStatVo.getCommentStatNum());
			//TODO:随机热度排名,用作测试
			albumInfoIndex.setHotScore(RandomUtil.randomDouble(0.0, 100.0, 4, RoundingMode.HALF_UP));
			//添加索引索引对象
			albumInfoIndexList.add(albumInfoIndex);
			//注册布隆过滤器存在该专辑
			bloomFilter.add(albumInfoIndex.getId());
		}
		//批量上架专辑
		albumInfoRespository.saveAll(albumInfoIndexList);
		List<SuggestIndex> suggestIndexList = new LinkedList<>();
		//注册专辑Suggesters建议器
		for (AlbumInfoIndex albumInfoIndex : albumInfoIndexList)
		{
			//创建专辑标题提词器
			SuggestIndex albumInfoTitleToSuggestIndex = this.albumInfoIndexToSuggestIndex(albumInfoIndex, AlbumInfoIndex::getAlbumTitle);
			//创建专辑简介提词器
			SuggestIndex albumInfoIntroSuggestIndex = this.albumInfoIndexToSuggestIndex(albumInfoIndex, AlbumInfoIndex::getAlbumIntro);
			//创建专辑创建用户提词器
			SuggestIndex albumInfoAnnouncerNameSuggestIndex = this.albumInfoIndexToSuggestIndex(albumInfoIndex, AlbumInfoIndex::getAnnouncerName);
			//依次添加专辑的提词器对象
			suggestIndexList.add(albumInfoTitleToSuggestIndex);
			suggestIndexList.add(albumInfoIntroSuggestIndex);
			suggestIndexList.add(albumInfoAnnouncerNameSuggestIndex);
		}
		//批量注册专辑的提词器对象
		albumInfoSuggestIndexRespository.saveAll(suggestIndexList);
	}

	@SneakyThrows
	@Override
	public AlbumSearchResponseVo searchAlbumInfo(AlbumIndexQuery albumIndexQuery) {
		AlbumSearchResponseVo albumSearchResponseVo = new AlbumSearchResponseVo();
		//获取查询关键字
		String keyword = albumIndexQuery.getKeyword();
		//获取排序规则
		String order = albumIndexQuery.getOrder();
		//获取查询属性列表 字符串格式:（属性id:属性值id）
		List<String> attributeList = albumIndexQuery.getAttributeList();
		//获取专辑分类
		Long category1Id = albumIndexQuery.getCategory1Id();
		Long category2Id = albumIndexQuery.getCategory2Id();
		Long category3Id = albumIndexQuery.getCategory3Id();
		//获取当前页数与页数大小
		Integer pageNo = albumIndexQuery.getPageNo();
		Integer pageSize = albumIndexQuery.getPageSize();
		//todo:创建查询构建器对象
		SearchRequest.Builder searchRequestbuilder = new SearchRequest.Builder().index(ELASTICSEARCH_INDEX_NAME);
		//todo:设置 页数 与 页数大小
		searchRequestbuilder.from((pageNo - 1) * pageSize);
		searchRequestbuilder.size(pageSize);
		//todo:判断是否开启专辑分类查询
		//todo:创建专辑分类构建器对象
		BoolQuery.Builder boolCategoryBuilder = new BoolQuery.Builder();
		//一级分类id
		if (category1Id != null && category1Id > 0)
		{
			boolCategoryBuilder.must(builder -> builder
					.match(builder1 -> builder1
							.field("category1Id")
							.query(category1Id)));
		}
		//二级分类id
		if (category2Id != null && category2Id > 0)
		{
			boolCategoryBuilder.must(builder -> builder
					.match(builder1 -> builder1
							.field("category2Id")
							.query(category2Id)));
		}
		//三级分类id
		if (category3Id != null && category3Id > 0)
		{
			boolCategoryBuilder.must(builder -> builder
					.match(builder1 -> builder1
							.field("category3Id")
							.query(category3Id)));
		}
		//构造分类bool查询
		BoolQuery boolQuery = boolCategoryBuilder.build();
		searchRequestbuilder.query(builder -> builder.bool(boolQuery));
		//todo:属性查询
		//todo:创建专辑属性构建器对象
		if (!CollectionUtils.isEmpty(attributeList))
		{
			NestedQuery.Builder nestedAttributeBuider = new NestedQuery.Builder().path("attributeValueIndexList");
			for (String s : attributeList)
			{
				//按照规定协议,获取属性id (0)与属性值id (1)
				String[] strings = s.split(":");
				String attributeId = strings[0];
				String attributeValueId = strings[1];
				nestedAttributeBuider.query(builder2 -> builder2
						.bool(builder3 -> builder3
								.must(builder4 -> builder4
										.match(builder5 -> builder5
												.field("attributeValueIndexList.attributeId")
												.query(attributeId)))
								.must(builder4 -> builder4
										.match(builder5 -> builder5
												.field("attributeValueIndexList.valueId")
												.query(attributeValueId)))));
			}
			//构造专辑属性nested查询
			NestedQuery nestedQuery = nestedAttributeBuider.build();
			searchRequestbuilder.query(builder -> builder.nested(nestedQuery));
		}
		//todo:关键字查询
		if (StringUtils.isNotBlank(keyword))
		{
			//todo:构造专辑关键字构建器对象
			MultiMatchQuery.Builder multiMatchKeywordbuilder = new MultiMatchQuery.Builder();
			multiMatchKeywordbuilder.query(keyword).fields(List.of("albumTitle", "albumIntro"));
			//构造专辑关键字multimatch查询
			MultiMatchQuery multiMatchQuery = multiMatchKeywordbuilder.build();
			searchRequestbuilder.query(builder -> builder.multiMatch(multiMatchQuery));
		}
		//todo:选择排序规则 （综合排序[1:desc] 播放量[2:desc] 发布时间[3:desc]；asc:升序 desc:降序）
		//构造专辑排序查询
		//todo:默认综合排序,按匹配度降序
		FieldSort.Builder fieldSortBuilder = new FieldSort.Builder().field("_score").order(SortOrder.Desc);
		if (StringUtils.isNotBlank(order))
		{
			String[] strings = order.split(":");
			String orderFiled = strings[0];
			String orderType = strings[1];
			switch (orderFiled)
			{
				case "1":
					fieldSortBuilder.field("_score");
					break;
				case "2":
					fieldSortBuilder.field("playStatNum");
					break;
				case "3":
					fieldSortBuilder.field("createTime");
					break;
				default:
			}
			fieldSortBuilder.order("asc".equals(orderType) ? SortOrder.Asc : SortOrder.Desc);
		}
		//构造专辑排序规则查询
		FieldSort fieldSort = fieldSortBuilder.build();
		searchRequestbuilder.sort(builder -> builder.field(fieldSort));
		//todo:启用高亮显示
		Highlight.Builder highlightBuilder = new Highlight.Builder();
		highlightBuilder
				.fields("albumTitle", builder -> builder
						.preTags("<span style=color:red>")
						.postTags("</span>"));
		Highlight highlight = highlightBuilder.build();
		searchRequestbuilder.highlight(highlight);
		//构造专辑查询
		SearchRequest searchRequest = searchRequestbuilder.build();
		SearchResponse<AlbumInfoIndex> albumInfoIndexSearchResponse = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);
		log.info(albumInfoIndexSearchResponse.hits().hits().toString());
		//构造返回结果集
		List<AlbumInfoIndexVo> albumInfoIndexVoList = new LinkedList<>();
		albumInfoIndexSearchResponse.hits().hits().forEach(albumInfoIndexHit -> {
			AlbumInfoIndex source = albumInfoIndexHit.source();
			AlbumInfoIndexVo albumInfoIndexVo = BeanUtil.toBean(source, AlbumInfoIndexVo.class);
			//设置高亮显示
			List<String> highlightAlbumTitleList = albumInfoIndexHit.highlight().get("albumTitle");
			if (!CollectionUtils.isEmpty(highlightAlbumTitleList))
				albumInfoIndexVo.setAlbumTitle(highlightAlbumTitleList.get(0));
			albumInfoIndexVoList.add(albumInfoIndexVo);
		});
		albumSearchResponseVo.setList(albumInfoIndexVoList);
		//设置页数大小
		albumSearchResponseVo.setPageSize(pageSize);
		//设置当前页面
		albumSearchResponseVo.setPageNo(pageNo);
		//设置总记录数
		long total = albumInfoIndexSearchResponse.hits().total().value();
		albumSearchResponseVo.setTotal(total);
		//设置总页数
		albumSearchResponseVo.setTotalPages((total + pageSize - 1) / pageSize);
		//返回结果集
		return albumSearchResponseVo;
	}

	@SneakyThrows
	@Override
	public List<String> completeSuggest(String keyword) {
		LinkedList<String> list = new LinkedList<>();
		//构建题词构造器
		Suggester.Builder suggestBuilder = new Suggester.Builder();
		//构建关键词建议构造器
		suggestBuilder.suggesters("suggest1", builder -> builder
				.prefix(keyword)
				.completion(builder1 -> builder1
						.field("keyword")
						.skipDuplicates(Boolean.TRUE)
						.fuzzy(builder2 -> builder2
								.fuzziness("auto"))));
		//构建关键词拼音建议构造器
		suggestBuilder.suggesters("suggest2", builder -> builder
				.prefix(keyword)
				.completion(builder1 -> builder1
						.field("keywordPinyin")
						.skipDuplicates(Boolean.TRUE)
						.fuzzy(builder2 -> builder2
								.fuzziness("auto"))));
		//构建关键词首字母拼音建议构造器
		suggestBuilder.suggesters("suggest3", builder -> builder
				.prefix(keyword)
				.completion(builder1 -> builder1
						.field("keywordSequence")
						.skipDuplicates(Boolean.TRUE)
						.fuzzy(builder2 -> builder2
								.fuzziness("auto"))));
		Suggester suggester = suggestBuilder.build();
		//开始根据关键词查询建议
		SearchResponse<SuggestIndex> searchResponse = elasticsearchClient.search(builder -> builder
				//选择查询索引
				.index("suggestinfo")
				//排除多余不必要字段
				.source(builder1 -> builder1
						.filter(builder2 -> builder2
								.excludes("keyword", "keywordPinyin", "keywordSequence")))
				.suggest(suggester), SuggestIndex.class);
		System.out.println(searchResponse);
		Map<String, List<Suggestion<SuggestIndex>>> suggest = searchResponse.suggest();
		//解析关键词
		parseSuggestInfo(searchResponse, "suggest1", list);
		//解析关键词全拼音
		parseSuggestInfo(searchResponse, "suggest2", list);
		//解析关键词首字母拼音
		parseSuggestInfo(searchResponse, "suggest3", list);
		//内部元素去重处理
		return list.stream().distinct().limit(SUGGEST_LIMIT_LENGTH).toList();
	}

	@SneakyThrows
	@Override
	public List<Map<String, Object>> getChannel(Long category1Id) {
		LinkedList<Map<String, Object>> resultList = new LinkedList<>();
		//根据一级分类id获取三级分类id集合
		Result<List<BaseCategory3>> baseCategory3Result = albumApiFeignClient.findBaseCategory3(category1Id);
		List<BaseCategory3> baseCategory3List = baseCategory3Result.getData();
		//整合为map, key为category3Id, value为baseCatrgory3
		Map<Long, BaseCategory3> baseCategory3Map = baseCategory3List.stream()
				.collect(Collectors
						.toMap(BaseCategory3::getId, baseCategory3 -> baseCategory3));
		//用于后续的多参数查询
		List<FieldValue> baseCategory3IdList = baseCategory3List.stream().map(baseCategory3 -> FieldValue.of(baseCategory3.getId())).toList();
		//根据三级分类id查询属于热度专辑的前6张专辑
		SearchResponse<AlbumInfoIndex> albumInfoIndexSearchResponse = elasticsearchClient.search(builder -> builder
						.index("albuminfo_index")
						.from(0)
						.size(0)
						.query(builder1 -> builder1
								.terms(builder2 -> builder2
										.field("category3Id")
										.terms(builder3 -> builder3
												.value(baseCategory3IdList))
								))
						.aggregations("groupByCategory3Id", builder1 -> builder1
								.terms(builder2 -> builder2
										.field("category3Id")
										.size(10))
								.aggregations("topHotAlbum", builder2 -> builder2
										.topHits(builder3 -> builder3
												.size(6)
												.sort(builder4 -> builder4.field(builder5 -> builder5
														.field("hotScore")
														.order(SortOrder.Desc))))))
				, AlbumInfoIndex.class);
		//从查询响应中提取数据
		Aggregate aggregate = albumInfoIndexSearchResponse.aggregations().get("groupByCategory3Id");
		//获取分类桶, 按三级分类id分组
		List<LongTermsBucket> category3List = aggregate.lterms().buckets().array();
		//便利桶中元素,依次获取三级分类id下的专辑列表
		for (LongTermsBucket longTermsBucket : category3List)
		{
			//key 为 三级分类id : value 为 当前三级分类下对应的专辑
			HashMap<String, Object> resultMap = new HashMap<>();
			//获取当前专辑的三级分类id
			long category3Id = longTermsBucket.key();
			//收集当前三级分类id下的专辑
			LinkedList<AlbumInfoIndex> albumInfoIndices = new LinkedList<>();
			//获取当前三级分类id下的专辑桶, 按hotScore排序且限制长度为6
			Aggregate albumAggregate = longTermsBucket.aggregations().get("topHotAlbum");
			//获取当前三级分类下 专辑的JsonData形式的列表
			List<Hit<JsonData>> albumListJson = albumAggregate.topHits().hits().hits();
			//将列表中的专辑JsonData反序列化为专辑对象
			albumListJson.forEach(albumJson -> {
				AlbumInfoIndex albumInfoIndex = JSONObject.parseObject(albumJson.source().toString(), AlbumInfoIndex.class);
				//收集反序列化后的专辑index实体
				albumInfoIndices.add(albumInfoIndex);
			});
			resultMap.put("baseCategory3", baseCategory3Map.get(category3Id));
			resultMap.put("list", albumInfoIndices);
			resultList.add(resultMap);
		}
		return resultList;
	}

	@SneakyThrows
	@Override
	public Map<String, Object> searchAlbumInfo(Long albumId) {
		//获取albumInfo
		Result<AlbumInfo> albumInfoResult = albumApiFeignClient.getAlbumInfoById(albumId);
		AlbumInfo albumInfo = albumInfoResult.getData();
		Assert.notNull(albumInfo, "专辑信息不为空");
		//announcer
		//获取作者id
		Long announcerId = albumInfo.getUserId();
		Result<UserInfoVo> userInfoVoResult = userApiFeignClient.getUserInfoForFeign(announcerId);
		UserInfoVo userInfoVo = userInfoVoResult.getData();
		Assert.notNull(userInfoVoResult, "专辑作者信息不为空");
		//获取albumStatVo
		Result<AlbumStatVo> albumStatVoResult = albumApiFeignClient.getAlbumStatById(albumId);
		AlbumStatVo albumStatVo = albumStatVoResult.getData();
		Assert.notNull(albumStatVo, "专辑统计信息不为空");
		//获取专辑分类:baseCategoryView
		Result<BaseCategoryView> baseCategoryViewResult = albumApiFeignClient.findCategoryByCategory3Id(albumInfo.getCategory3Id());
		BaseCategoryView categoryView = baseCategoryViewResult.getData();
		Assert.notNull(categoryView, "专辑分类不为空");
		//整合查询数据
		HashMap<String, Object> resultMap = new HashMap<>();
		resultMap.put("albumInfo", albumInfo);
		resultMap.put("announcer", userInfoVo);
		resultMap.put("baseCategoryView", categoryView);
		resultMap.put("albumStatVo", albumStatVo);
		return resultMap;
	}

	@Override
	public List<AlbumInfoIndexVo> findRankingList(Long category1Id, String statType) {
		return JSONObject.parseObject((String) redisTemplate.opsForHash().get(RedisConstant.RANKING_KEY_PREFIX + category1Id, statType), List.class);
	}

	@SneakyThrows
	@Override
	public void updateLatelyAlbumRanking() {
		//获取所有category1Id
		Result<List<Long>> category1IdListResult = albumApiFeignClient.getCategory1IdAll();
		List<Long> category1IdList = category1IdListResult.getData();
		Assert.notNull(category1IdList, "一级分类id集合不为空");
		// todo:手动调用一次更新，查看排行榜。后续会整合 xxl-job 分布式定时任务调度框架做定时调用
		for (Long category1Id : category1IdList)
		{
			//根据一级分类id依次获取不同专辑统计分类下排名
			//todo:专辑统计 0401-播放量 0402-订阅量 0403-购买量 0404-评论数
			//0401-播放量
			saveAlbumCategory1RankingByStatTypeInMap("playStatNum", category1Id);
			//0402-订阅量
			saveAlbumCategory1RankingByStatTypeInMap("subscribeStatNum", category1Id);
			//0403-购买量
			saveAlbumCategory1RankingByStatTypeInMap("buyStatNum", category1Id);
			//0404-评论数
			saveAlbumCategory1RankingByStatTypeInMap("commentStatNum", category1Id);
			//hotScore-按热度
			saveAlbumCategory1RankingByStatTypeInMap("hotScore", category1Id);
		}

	}

	private void saveAlbumCategory1RankingByStatTypeInMap(String statType, Long category1Id) throws IOException {
		//从es缓存中获取对应统计类型的排名数据
		SearchResponse<AlbumInfoIndex> albumInfoIndexSearchResponse = elasticsearchClient.search(new SearchRequest.Builder()
				.index("albuminfo_index")
				.size(10)
				.query(builder -> builder
						.terms(builder1 -> builder1
								.field("category1Id")
								.terms(builder2 -> builder2
										.value(List.of(FieldValue.of(category1Id))))))
				.sort(builder -> builder
						.field(builder1 -> builder1
								.field(statType)
								.order(SortOrder.Desc))).build(), AlbumInfoIndex.class);
		log.info(albumInfoIndexSearchResponse.hits().hits().toString());
		//在对应一级分类排名缓存中保存指定统计类型专辑排序
		redisTemplate.opsForHash().put(RedisConstant.RANKING_KEY_PREFIX + category1Id
				, statType
				, JSONObject.toJSONString(albumInfoIndexSearchResponse
						.hits()
						.hits()
						.stream()
						.map(albumInfoIndexHit -> albumInfoIndexHit.source())
						.toList()));
	}

	SuggestIndex albumInfoIndexToSuggestIndex(AlbumInfoIndex albumInfoIndex, Function<AlbumInfoIndex, String> getSuggestField) {
		SuggestIndex suggestIndex = new SuggestIndex();
		//uuid随机生成,用于测试
		suggestIndex.setId(UUID.fastUUID().toString());
		String keyword = getSuggestField.apply(albumInfoIndex);
		suggestIndex.setTitle(keyword);
		if (StringUtils.isNotBlank(keyword))
		{
			//填充字段
			suggestIndex.setKeyword(new Completion(new String[]{keyword}));
			//指定字段转拼音
			suggestIndex.setKeywordPinyin(new Completion(new String[]{PinYinUtils.toHanyuPinyin(keyword)}));
			//指定字段转首字母拼音
			suggestIndex.setKeywordSequence(new Completion(new String[]{PinYinUtils.getFirstLetter(keyword)}));
		}
		return suggestIndex;
	}

	void parseSuggestInfo(SearchResponse<SuggestIndex> searchResponse, String suggestMapKey, List<String> list) {
		//根据定义的关键词字段Key获取当前字段建议Map
		Map<String, List<Suggestion<SuggestIndex>>> suggestMap = searchResponse.suggest();
		//获取建议集合
		List<Suggestion<SuggestIndex>> suggestions = suggestMap.get(suggestMapKey);
		//集合只有一层建议
		Suggestion<SuggestIndex> suggestIndexSuggestion = suggestions.get(0);
		//获取建议选择列表
		List<CompletionSuggestOption<SuggestIndex>> options = suggestIndexSuggestion.completion().options();
		if (options.isEmpty())
		{
			return;
		}
		//便利所有建议获取建议所属标题
		options.forEach(Object -> list.add(Object.source().getTitle()));
	}

}
